import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, KFold, cross_validate, cross_val_score, cross_val_predict
from sklearn.metrics import confusion_matrix, plot_confusion_matrix
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
import dalex as dx
full_data = pd.read_csv("hotel_bookings.csv")
full_data["agent"] = full_data["agent"].astype(str)
treshold = 0.005 * len(full_data)
agents_to_change = full_data['agent'].value_counts()[full_data['agent'].value_counts() < treshold].index
full_data.loc[full_data["agent"].isin(agents_to_change), "agent"] = "other"
countries_to_change = full_data['country'].value_counts()[full_data['country'].value_counts() < treshold].index
full_data.loc[full_data["country"].isin(countries_to_change), "country"] = "other"
# Określenie cech uwzględnionych w modelu
num_features = ["lead_time", "arrival_date_week_number",
"stays_in_weekend_nights", "stays_in_week_nights",
"adults", "previous_cancellations",
"previous_bookings_not_canceled",
"required_car_parking_spaces", "total_of_special_requests",
"adr", "booking_changes"]
cat_features = ["hotel", "market_segment", "country",
"reserved_room_type",
"customer_type", "agent"]
features = num_features + cat_features
# Podział na zmienne wyjaśniające i target
X = full_data.drop(["is_canceled"], axis=1)[features]
y = full_data["is_canceled"]
categorical_names = {}
for feature in cat_features:
col = X[[feature]]
cat_transformer = SimpleImputer(strategy="constant", fill_value="Unknown")
col = cat_transformer.fit_transform(col)
X[feature] = col
le = LabelEncoder()
le.fit(X[[feature]])
X[[feature]] = le.transform(X[[feature]])
categorical_names[feature] = le.classes_
# Preprocessing
num_transformer = SimpleImputer(strategy="constant")
preprocessor = ColumnTransformer(transformers=[("num", num_transformer, num_features)],
remainder = 'passthrough')
c:\users\artur\appdata\local\programs\python\python39\lib\site-packages\sklearn\utils\validation.py:63: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel(). return f(*args, **kwargs) c:\users\artur\appdata\local\programs\python\python39\lib\site-packages\sklearn\utils\validation.py:63: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel(). return f(*args, **kwargs) c:\users\artur\appdata\local\programs\python\python39\lib\site-packages\sklearn\utils\validation.py:63: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel(). return f(*args, **kwargs) c:\users\artur\appdata\local\programs\python\python39\lib\site-packages\sklearn\utils\validation.py:63: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel(). return f(*args, **kwargs) c:\users\artur\appdata\local\programs\python\python39\lib\site-packages\sklearn\utils\validation.py:63: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel(). return f(*args, **kwargs) c:\users\artur\appdata\local\programs\python\python39\lib\site-packages\sklearn\utils\validation.py:63: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel(). return f(*args, **kwargs)
# Stworzenie modelu
rf_model_enh = RandomForestClassifier(n_estimators=160,
max_features=0.4,
min_samples_split=2,
n_jobs=-1,
random_state=42)
model_pipe = Pipeline(steps=[('preprocessor', preprocessor),
('model', rf_model_enh)])
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2, random_state=42)
model_pipe.fit(X_train, y_train)
Pipeline(steps=[('preprocessor',
ColumnTransformer(remainder='passthrough',
transformers=[('num',
SimpleImputer(strategy='constant'),
['lead_time',
'arrival_date_week_number',
'stays_in_weekend_nights',
'stays_in_week_nights',
'adults',
'previous_cancellations',
'previous_bookings_not_canceled',
'required_car_parking_spaces',
'total_of_special_requests',
'adr',
'booking_changes'])])),
('model',
RandomForestClassifier(max_features=0.4, n_estimators=160,
n_jobs=-1, random_state=42))])
selected_X = X_train.loc[[32]]
selected_y = y_train[32]
predicted_y = rf_model_enh.predict(selected_X)
print(selected_y, predicted_y)
selected_X.head()
1 [1]
| lead_time | arrival_date_week_number | stays_in_weekend_nights | stays_in_week_nights | adults | previous_cancellations | previous_bookings_not_canceled | required_car_parking_spaces | total_of_special_requests | adr | booking_changes | hotel | market_segment | country | reserved_room_type | customer_type | agent | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 32 | 96 | 27 | 2 | 8 | 2 | 0 | 0 | 0 | 2 | 108.3 | 0 | 1 | 3 | 16 | 4 | 2 | 21 |
explainer = dx.Explainer(rf_model_enh, X_train, y_train, label = "Random Forest")
Preparation of a new explainer is initiated -> data : 95512 rows 17 cols -> target variable : Parameter 'y' was a pandas.Series. Converted to a numpy.ndarray. -> target variable : 95512 values -> model_class : sklearn.ensemble._forest.RandomForestClassifier (default) -> label : Random Forest -> predict function : <function yhat_proba_default at 0x00000249261EE670> will be used (default) -> predict function : Accepts pandas.DataFrame and numpy.ndarray. -> predicted values : min = 0.0, mean = 0.371, max = 1.0 -> model type : classification will be used (default) -> residual function : difference between y and yhat (default) -> residuals : min = -0.943, mean = -0.00218, max = 0.957 -> model_info : package sklearn A new explainer has been created!
ps = explainer.predict_surrogate(selected_X, type = "lime",
categorical_names=categorical_names,
categorical_features=range(11, 17),
show_all=True)
ps.show_in_notebook()
Możemy zauważyć, że brak wcześniejszych anulowań rezerwacji ma w tym wypadku największy wpływ na predykcję. Mimo to, model jest w stanie, na podstawie innych czynników, pozytywnie przewidzieć ostateczny rezultat (sprzeczny z wpływem dominującej zmiennej). Widzimy jednak, że wpływ tej zmiennej jest istotny i wpływa na brak pewności w predykcji modelu.
x_1 = X_train.loc[[289]]
x_2 = X_train.loc[[999]]
x_3 = X_train.loc[[2626]]
ps = explainer.predict_surrogate(x_1, type = "lime",
categorical_names=categorical_names,
categorical_features=range(11, 17),
show_all=True)
ps.show_in_notebook()
Co ciekawe przy wyjaśnianiu z użyciem predicts parts brak wcześniejszego anulowania było dopiero 7 z kolei zmienną. W tym wypadku jest wyraźnie dominująca i mimo odmiennego wpływu innych zmiennych przeważyła ona ostateczną predykcję modelu. Wizualnie wydawać by się mogło, że zmienne wpływające na predykcję=1 przeważą, lecz ostateczny wynik jest inny.
ps = explainer.predict_surrogate(x_2, type = "lime",
categorical_names=categorical_names,
categorical_features=range(11, 17),
show_all=True)
ps.show_in_notebook()
Również w tym wypadku brak wcześniejszej anulacji ma zdecydowanie większy wpływ przy użyciu metody LIME niż predict parts. Zauważalny również jest wzrost znaczenia kraju pochodzenia osoby skłądającej rezerwację. Choć znaczenie zmiennej required_car_parking w obu przypadkach miało jeden z największych wpływów na predykcję. Mimo znacznego, przeciwnego do predykcji modelu, wkłądu required_car_parking, model ostatecznie z prawdopodobieństwem równym 1 przewiduje brak anulowania rezerwacji.
ps = explainer.predict_surrogate(x_3, type = "lime",
categorical_names=categorical_names,
categorical_features=range(11, 17),
show_all=True)
ps.show_in_notebook()
W tym wypadku także znacząco wzrósł wkład previous_cancellations w porównaniu do metody predict parts. Możemy jednak zauważyć, że tym razem zmienna ta nie zaważyła na końcowej predykcji modelu, co więcej prawdopodobieństwo przeciwnego do wkładu tej zmiennej wyniku jest równe 1. Możemy również zauważyć znaczący wzrost kraju pochodznia, tak jak w przypadku poprzedniej obserwacji. Co ciekawe porównując do obserwacji x_1 wydaje się, że mamy tu większy wpływ na predykcję=0, lecz ostatecznie dostajemy predykcję=1 z prawdopodobieńśtwem=1, co jest bardzo zaskakujące (porównując wykres i tabelkę dla obu obserwacji). Porównując wkłady kolejnych zmiennych w obu przedstawionych obserwacjach, możemy zauważyć, że przy takich samych wartościach wkład w ostateczny wynik jest bardzo do siebie zbliżony, lecz nie identyczny. Wydaje się więc, że lime jest stabilną metodą.
Wnioski: